#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "isa.h"
#include "assembler.h"

static struct {
  char name[20];	// label's name
  void *addr;		// label's addr
}label_table[10];	// store label's infomation

struct {
  int id;
  char instr;
  char reg[2];
  int reg_index;
  int imme;
} statement = 	// temporarily store statement represented by TOKENS
{ 0, 0, {0xf, 0xf}, 0, 0};

int addr = 0;	// the address of instruction
int pass;	// pass 1: write statement(STRUCT) to outfile EXCEPT label's addr, pass 2:write label's addr to outfile
extern FILE *yyin;	// source file
FILE *outfile;		// target file
int yylex();		// generate by flex

/*
 * printSymbols: just used for debug
 */
/*************** DEBUG ONLY ***********************
void printSymbols(void){
  for(int i=0;i<10;i++){
    if(0 != strlen(label_table[i].name)){
    printf("%s: %#8x\n",label_table[i].name, label_table[i].addr);
    }
  }
}
***************************************************/

/*
 * add_reg: add register to statement(STRUCT)
 * @s: pointer of string which represent a register
 */
void add_reg(char *s){
  for(int i=0;i<REG_NONE;i++){
    if(0 == strcmp(reg_table[i].name, s)){
      statement.reg[statement.reg_index] = i;
      statement.reg_index = statement.reg_index ? 0 : 1;
    }
  }
}

/*
 * add_instr: add instruction to statement(STRUCT)
 * @s: pointer of string which represent a instruction
 */
void add_instr(char *s){
  for(int i=0;i<I_NONE;i++){
    if(0 == strcmp(instr_table[i].name, s)){
      statement.id = i;
      statement.instr = instr_table[i].value;
    }
  }
}

/*
 * add_letter: add letter to statement(STRUCT)
 * @c: pointer of string which represent a letter
 */
void add_letter(char *c){
  statement.imme = (int) c[1];
}

/*
 * atoih: translate string to integer
 * @c: string that can be translated to integer
 * return: integer
 */
int atoih(char *c){
  return strtol(c, NULL, 16);
}

/*
 * add_hex: add hexadecimal to statement(STRUCT)
 * @c: the hexadecimal
 */
void add_hex(char *c){
  statement.imme = atoih(c);
  //printf("add hex:%s --> %#x\n",c, statement.imme);
}

/*
 * add_dec: add decimal to statement(STRUCT)
 * @c: the decimal
 */
void add_dec(char *c){
  statement.imme = atoi(c);
  //printf("add dec:%s --> %d\n",c, statement.imme);
}

/*
 * add_addr: add address to statement(STRUCT)
 * @c: the label's name which can be transfer to address
 */
void add_addr(char *c){
  if(1 == pass){
    statement.imme = 0x11111111;
    return;
  }
  for(int i=0;i<10;i++){
    if(0 == strcmp(c, label_table[i].name)){
      statement.imme = label_table[i].addr;
      return;
    }
  }
  printf("err:no label's name matches %s\n",c);
}

/*
 * add_label: add label to label_table(STRUCT)
 * @c: the label's name
 */
void add_label(char *c){
  int str_len;

  if(2 == pass) return;
  for(int i=0;i<10;i++){
    str_len = strlen(label_table[i].name);
    if(0 == str_len){
      str_len = strlen(c);
      strncpy(label_table[i].name, c, str_len-1);
      label_table[i].addr = addr;
      // printf("label name:%s, label addr:%#x\n",label_table[i].name, label_table[i].addr);
      return;
    }
  }

  printf("err:label_table has no place to install more label!\n");
}

/*
 * add_stmt: add statement(STRUCT) to outfile(FILE)
 */
void add_stmt(){
  char temp_byte;

  // printf("add stmt:%#x %#x,%#x,%#x\n",statement.instr, statement.reg[0], statement.reg[1], statement.imme);
  switch(statement.id){
  case I_HALT:
    fwrite(&statement.instr, 1, 1, outfile);
    addr++;
    break;
  case I_NOP:
    fwrite(&statement.instr, 1, 1, outfile);
    addr++;
    break;
  case I_RRMOV:
    fwrite(&statement.instr, 1, 1, outfile);
    temp_byte = (statement.reg[0]<<4) + statement.reg[1];
    fwrite(&temp_byte, 1, 1, outfile);
    statement.reg_index = 0;
    addr += 2;
    break; 
  case I_IRMOV:
    fwrite(&statement.instr, 1, 1, outfile);
    temp_byte = statement.reg[0] | 0xF0;
    fwrite(&statement.reg[0], 1, 1, outfile);
    fwrite(&statement.imme, 4, 1, outfile);
    statement.reg_index = 0;
    addr += 6;
    break;
  case I_STORE:
    fwrite(&statement.instr, 1, 1, outfile); 
    temp_byte = (statement.reg[0]<<4) + statement.reg[1];
    fwrite(&temp_byte, 1, 1, outfile);
    fwrite(&statement.imme, 4, 1, outfile);
    statement.reg_index = 0;
    addr += 6;
    break;   
  case I_LOAD:
    fwrite(&statement.instr, 1, 1, outfile);
    temp_byte = (statement.reg[0]<<4) + statement.reg[1];
    fwrite(&temp_byte, 1, 1, outfile);
    fwrite(&statement.imme, 4, 1, outfile);
    statement.reg_index = 0;
    addr += 6;
    break;
  case I_ADD:
  case I_SUB:
  case I_AND:
  case I_OR:
  case I_NOT:
  case I_XOR:
  case I_SHL:
  case I_SHR:
    fwrite(&statement.instr, 1, 1, outfile);
    temp_byte = (statement.reg[0]<<4) + statement.reg[1];
    fwrite(&temp_byte, 1, 1, outfile);
    statement.reg_index = 0;
    addr += 2;
    break;
  case I_JMP:
  case I_JE:
  case I_JL:
  case I_JG:
  case I_JNE:
  case I_JLE:
  case I_JGE:
  case I_CALL:
    fwrite(&statement.instr, 1, 1, outfile);
    fwrite(&statement.imme, 4, 1, outfile);
    addr += 5;
    break;
  case I_INT:
    fwrite(&statement.instr, 1, 1, outfile);
    fwrite(&statement.imme, 1, 1, outfile);
    addr += 2;
    break;
  case I_RET:
  case I_IRET:
    fwrite(&statement.instr, 1, 1, outfile);
    addr += 1;
    break;
  case I_PUSH:
  case I_POP:
    fwrite(&statement.instr, 1, 1, outfile);
    temp_byte = statement.reg[0]<<4;
    fwrite(&temp_byte, 1, 1, outfile);
    statement.reg_index = 0;
    addr += 2;
    break;
  case I_IN:
    fwrite(&statement.instr, 1, 1, outfile);
    temp_byte = statement.reg[0] | 0xF0;
    fwrite(&temp_byte, 1, 1, outfile);
    temp_byte = (char) statement.imme;
    fwrite(&temp_byte, 1, 1, outfile);
    statement.reg_index = 0;
    addr += 3;
    break;
  case I_OUT:
    fwrite(&statement.instr, 1, 1, outfile);
    temp_byte = statement.reg[0] << 4 | 0xF;
    fwrite(&temp_byte, 1, 1, outfile);
    temp_byte = (char) statement.imme;
    fwrite(&temp_byte, 1, 1, outfile);
    statement.reg_index = 0;
    addr += 3;
    break;
  default:
    printf("stmt err! %x %x %x %x\n",statement.instr, statement.reg[0],
	statement.reg[1], statement.imme);
  }
}

int main(int argc, char **argv){
  int rootlen;		// length of source file's name
  char infname[512];	// store the name of source file
  char outfname[512];	// store the name of target file

  /* get filename of source file */
  rootlen = strlen(argv[1]) - 2;
  strcpy(infname, argv[1]);

  /* open source file */
  yyin = fopen(infname, "r");
  if(yyin == NULL){
    printf("open fail :%s\n",infname);
    exit(1);
  }

  /* open target file */
  strncpy(outfname, argv[1], rootlen);
  strcpy(outfname + rootlen, ".o");
  outfile = fopen(outfname, "wb");
  if(outfile == NULL){
    printf("open fail: %s\n",outfname);
    exit(1);
  }

  /* pass 1:analysis source file and write to target file EXCEPT symbols*/
  pass = 1;
  yylex();
  fclose(yyin);
  fclose(outfile);
  // printSymbols();

  /* open source file */
  yyin = fopen(infname, "r");
  if(yyin == NULL){
    printf("open fail :%s\n",infname);
    exit(1);
  }

  /* open target file */
  outfile = fopen(outfname, "wb");
  if(outfile == NULL){
    printf("open fail: %s\n",outfname);
    exit(1);
  }

  /* pass 2:write symbols to target file*/
  pass = 2;
  yylex();
  // printSymbols();

  /* close source file and target file */
  fclose(yyin);
  fclose(outfile);
}
